ThreadLocal为开发者提供了线程安全的另外一种思路:synchronized关注的是线程间数据的共享,而ThreadLocal关注的是线程间数据的隔离。
Why
当我们使用诸如synchronized、volatile等手段对线程进行同步时,多个线程会访问同样的变量,针对这种情况我们需要严格限制变量的访问,比如竞争、加锁、释放锁等操作,编码复杂度较高。
ThreadLocal提供了另外一种思路,顾名思义就是使用线程本地的变量,即每个ThreadLocal实例存储着只能被该线程访问和修改的变量,其他的线程无法访问。
How-To
ThreadLocal包含以下方法:
1 | // 创建, 无默认值 |
使用起来非常简单,通常只需在线程的run方法内进行取值及设值即可。
How
ThreadLocal能保证线程中的变量不被其他线程干扰,让我们看看源码是如何做到的。
我们先来看Thread类,Thread类实例持有一个成员变量,该变量为ThreadLocal类的内部类ThreadLocalMap的实例:
1 | package java.lang; |
而ThreadLocal类的内部类ThreadLocalMap里面又定义了一个内部类Entry。Entry类继承自WeakReference类,其泛型为ThreadLocal,这便是存储变量的位置:
1 | package java.lang; |
让我们看看如何设值:
1 | /** 返回线程持有的ThreadLocalMap实例(成员变量threadLocals) **/ |
而对应的取值代码:
1 | /** 设默认值 **/ |
最后是清空:
1 | public void remove() { |
总而言之,往ThreadLocal实例设值时:
- 从当前线程获取其持有的ThreadLocalMap实例,将ThreadLocalMap实例的key设为ThreadLocal实例自身,value为要存储的变量。
- 如果没有则创建ThreadLocalMap实例,同样地设值其key和value,并让当前线程持有该ThreadLocalMap实例。
从ThreadLocal实例取值时:
- 读取当前线程所持ThreadLocalMap实例成员变量,再从该ThreadLocalMap实例中以当前ThreadLocal实例为key找到对应的value。
子线程
当使用ThreadLocal来保存变量时,其保存在ThreadLocalMap中的变量只有持有该map的变量可以访问。
而我们可以用InheritableThreadLocal,其保存的变量不仅当前线程可以访问,还可以在子线程中被访问,从而实现了变量的传递。
1 | public static void main(String[] args) { |
内存泄漏
当使用ThreadLocal保存变量时,线程持有ThreadLocalMap实例,因此当线程由线程池管理时,由于线程存在复用,因此ThreadLocalMap实例不会消失,ThreadLocalMap中Entry的ThreadLocal实例key和value会被一直持有,从而导致内存泄漏。
然而,这看似经得起推敲的结论并不正确,因为从Entry的源码可以看到,key并不是强引用的ThreadLocal实例,而是一个指向该实例的WeakReference。
当一个对象失去所有强引用,只有弱引用时,该对象会被GC标记为可回收;当ThreadLocal实例key被回收后,ThreadLocalMap中会存在key为null的Entry,同时get/set/remove方法均会清理key为null的Entry。
由这些措施,ThreadLocal基本避免了内存泄漏的隐患,但开发人员仍应在使用完ThreadLocal后,调用其remove方法进行手动清理。
Where
从ThreadLocal的原理来看,其使用场景可以用一句话总结:使用线程局部变量。
因此可以推断使用场景包含如下几种:
- 数据库连接。
- Session管理。
- 事务管理。